//
// Created by wxd on 2025/7/19.
//

#pragma once

#include <iostream>
#include <vector>
#include <glm/glm.hpp>
#include <glm/trigonometric.hpp>
#include "../wrapper/log/Logger.h"
#include "../wrapper/monitor/GLDebug.hpp"
#include "../global/ChineseOutput.hpp"
#include "../global/EngineDataProperties.h"
#include "../application/app/Application.h"
#include "../glFramework/shader/Shader.h"
#include "../glFramework/constant/engine_layout.h"
#include "../glFramework/texture/Texture.h"
#include "../glFramework/utils/ResourceLoadUtils.h"
#include "../glFramework/bo/VBO.h"
#include "../glFramework/bo/VAO.h"
#include "../glFramework/constant/engine_uniform.h"
#include <memory>

#include "bo/EBO.h"
#include "../global/output.hpp"

// Note: 约定矩阵乘法的顺序：trans * rot * translation (Unity引擎中也是采样这种顺序)

struct Block {
    float speed;      // 方块移动的速度
    float len;        // 方块的边长
    glm::vec2 center; // 方块中心坐标

    Block() : speed(0.f), len(0.f), center(glm::vec2(0.f, 0.f)) {}
    Block(const float speed, const float len, glm::vec2& center) : speed(speed), len(len), center(center) {}
};

inline Block block;
inline float currentFrameTime = 0.f;
inline float lastFrameTime = 0.f;
inline float deltaFrameTime = 0.f;
inline glm::mat4 rotMatrix = glm::mat4(1.f);
inline glm::mat4 transMatrix = glm::mat4(1.f);
inline glm::mat4 scaleMatrix = glm::mat4(1.f);
inline glm::mat4 modelMatrix = glm::mat4(1.f);
inline const char* vertexPath = "../demo/shader/_007/vertex_shader.glsl";
inline const char* fragmentPath = "../demo/shader/_007/fragment_shader.glsl";
inline const char* texPath = "../demo/texture/earth.png";

inline bool isOutOfArea() {
    const float half = block.len * 0.5f;

    const float yEdge = 1.0f / EngineApp.getWindowAspectRatio();

    // 添加微小容差（epsilon）避免浮点误差
    const float epsilon = 1e-5f;

    return (block.center.x + half > 1.0f + epsilon) ||
           (block.center.x - half < -1.0f - epsilon) ||
           (block.center.y + half > yEdge + epsilon) ||
           (block.center.y - half < -yEdge - epsilon);
}

inline void updateRotMatrix() {
    rotMatrix = glm::rotate(rotMatrix, glm::radians(0.01f), glm::vec3(0.f, 0.f, 1.f));
}

inline void updateTransMatrix() {
    transMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(block.center, 0.0f));
}

inline void updateScaleMatrix() {
    float s = glm::abs(glm::cos(glm::radians(15.0f * currentFrameTime))) + 0.75;
    scaleMatrix = glm::scale(glm::mat4(1.0f), glm::vec3(s, s, 0.f));
    block.len *= s;
}

inline void updateModelMatrix() {
    updateTransMatrix();
    modelMatrix = transMatrix * rotMatrix * scaleMatrix;
}

inline void doBlockMove(int dirType) {
    float speed = deltaFrameTime * block.speed;
    const float yEdge = 1.0f / EngineApp.getWindowAspectRatio();
    float half = block.len * 0.5f;
    switch (dirType) {
        case GLFW_KEY_W: // up
            block.center.y += speed;
            if (isOutOfArea()) {
                block.center.y = yEdge - half;
            }
            break;
        case GLFW_KEY_S: // down
            block.center.y -= speed;
            if (isOutOfArea()) {
                block.center.y = -yEdge + half;
            }
            break;
        case GLFW_KEY_A: // left
            block.center.x -= speed;
            if (isOutOfArea()) {
                block.center.x = -1.f + half;
            }
            break;
        case GLFW_KEY_D: // right
            block.center.x += speed;
            if (isOutOfArea()) {
                block.center.x = 1.f - half;
            }
            break;
        default:
            break;
    }

    std::cout << block.center.x << " " << block.center.y << std::endl;
}

inline void onResize(int width, int height) {
    glViewport(0, 0, width, height);
}

inline void onKeyBoard(int key, int action, int mods) {
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
        glfwSetWindowShouldClose(EngineApp.getCurrentWindow(), GLFW_TRUE);
    } else if (key == GLFW_KEY_W || key == GLFW_KEY_S || key == GLFW_KEY_A || key == GLFW_KEY_D) {
        doBlockMove(key);
    }
}

inline void render() {
    ChineseOutput::init();
    EngineApp.initDefaultWindowConfig();
    EngineApp.setResizeCallback(onResize);
    EngineApp.setKeyBoardCallback(onKeyBoard);
    EngineApp.init();

    // init block
    block.len = 0.07f;
    block.speed = 30.0f;
    block.center = glm::vec2(0.f, 0.f);
    const float half = block.len / 2.f;

    // raw data
    std::vector<float> vertices; // 顶点坐标 + 颜色 + 纹理坐标
    vertices.reserve(32);
    vertices.push_back(block.center.x + half);
    vertices.push_back(block.center.y + half);
    vertices.push_back(0.f);
    vertices.push_back(1.f);
    vertices.push_back(0.f);
    vertices.push_back(0.f);
    vertices.push_back(1.f);
    vertices.push_back(1.f);

    vertices.push_back(block.center.x + half);
    vertices.push_back(block.center.y - half);
    vertices.push_back(0.f);
    vertices.push_back(0.f);
    vertices.push_back(1.f);
    vertices.push_back(0.f);
    vertices.push_back(1.f);
    vertices.push_back(0.f);

    vertices.push_back(block.center.x - half);
    vertices.push_back(block.center.y - half);
    vertices.push_back(0.f);
    vertices.push_back(0.f);
    vertices.push_back(0.f);
    vertices.push_back(1.f);
    vertices.push_back(0.f);
    vertices.push_back(0.f);

    vertices.push_back(block.center.x - half);
    vertices.push_back(block.center.y + half);
    vertices.push_back(0.f);
    vertices.push_back(1.f);
    vertices.push_back(0.f);
    vertices.push_back(0.f);
    vertices.push_back(0.f);
    vertices.push_back(1.f);


    std::vector<unsigned int> indices;
    indices.reserve(6);
    indices.push_back(0);
    indices.push_back(1);
    indices.push_back(2);
    indices.push_back(0);
    indices.push_back(2);
    indices.push_back(3);


    // bo
    VAO vao;
    vao.bind();
    VBO vbo(vertices);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, fSize(8), reinterpret_cast<void *>(0));
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, fSize(8), reinterpret_cast<void *>(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, fSize(8), reinterpret_cast<void *>(6 * sizeof(float)));
    glEnableVertexAttribArray(2);
    EBO ebo(indices);
    vao.unbind();

    // shader
    std::unique_ptr<Shader> shader = std::make_unique<Shader>(vertexPath, fragmentPath);

    // texture
    Texture texture(texPath);
    texture.setSamplerName(EngineUniform::uTexUnit);

    std::string uBlockName = "uBlock";
    std::string uModelName = "uModel";

    while (EngineApp.update()) {
        GL_CALL(glClear(GL_COLOR_BUFFER_BIT))

        shader->bindProgram();

        currentFrameTime = static_cast<float>(glfwGetTime());

        if (lastFrameTime > 0.f) {
            deltaFrameTime = currentFrameTime - lastFrameTime;
        }

        lastFrameTime = currentFrameTime;

        // 更新矩阵
        updateModelMatrix();

        shader->setUniform(EngineUniform::uW, static_cast<float>(EngineApp.getCurrentWindowWidth()));
        shader->setUniform(EngineUniform::uH, static_cast<float>(EngineApp.getCurrentWindowHeight()));
        shader->setUniform(texture.getSamplerName(), texture.getTextureUnit()); // 采样0号单元

        shader->setUniform(uModelName, modelMatrix);

        vao.bind();
        GL_CALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0))
        vao.unbind();

        shader->unbindProgram();
    }

    shader->deleteProgram();
    vao.destroy();
    vbo.destroy();
    ebo.destroy();
    EngineApp.destroy();
}